package com.bupt.uchouten.common.skill;

import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.level.Level;

import javax.annotation.Nullable;

public abstract class Skill  {
    /**
     * The sound(s) played when this skill is cast.
     */
    @Nullable
    protected final SoundEvent[] sounds;
    /**
     * The volume of the sound played when this skill is cast. Defaults to 1.
     */
    protected float volume = 1;
    /**
     * The pitch of the sound played when this skill is cast. Defaults to 1.
     */
    protected float pitch = 1;
    /**
     * The pitch variation of the sound played when this skill is cast. Defaults to 0.
     */
    protected float pitchVariation = 0;

    /**
     * This constructor should be called from any subclasses, either feeding in the constants directly or through their
     * own constructor from wherever the skill is registered.
     */
    public Skill() {
        this.sounds = createSounds();
    }

    // ========================================= Initialisation methods ===========================================

    /**
     * Called from the constructor to initialise this skill's sounds. By default, this creates and returns a 1-element
     * array containing a single sound event. Override this to add a custom sound array, perhaps using one of the
     * convenience methods (see below).
     *
     * @return An array of sound events played by this skill.
     * @see Skill#playSound(Level, double, double, double)
     */
    protected SoundEvent[] createSounds() {
        return null;
    }

    // ============================================ Casting methods ==============================================

    /**
     * Casts the skill. Each subclass must override this method and within it execute the code to make the skill work.
     * Returns a boolean so that the main onItemRightClick or onUsingItemTick method can check if the skill was actually
     * cast or whether a skill specific condition caused it not to be (for example, heal won't work if the player is on
     * full health), preventing unfair drain of mana.
     * <p></p>
     * Note that (!world.isRemote) does not count as a condition;
     * return true should be outside it - in other words, return a value on both the client and the server.
     * <p></p>
     * It's worth noting that on the client side, this method only gets called if the server side cast() method
     * succeeded, so you can put any particle spawning code outside of any success conditions if there are discrepancies
     * between client and server.
     *
     * @param world  The world in which the skill is being cast.
     * @param caster The EntityPlayer that cast the skill.
     */
    public abstract boolean cast(Level world, Player caster);

    // ============================================ Sound methods ==============================================

    /**
     * Sets the sound parameters for this skill.
     *
     * @param volume         The volume of the sound played by this skill, relative to 1.
     * @param pitch          The pitch of the sound played by this skill, relative to 1.
     * @param pitchVariation The random variation in the pitch of the sound played by this skill. The pitch at which the
     *                       sound is played will be randomly chosen from the range: {@code pitch +/- pitchVariation}.
     * @return The skill instance, allowing this method to be chained onto the constructor. Note that since this method
     * only returns a {@code skill}, if you are chaining multiple methods onto the constructor this should be called last.
     */
    public Skill soundValues(float volume, float pitch, float pitchVariation) {
        this.volume = volume;
        this.pitch = pitch;
        this.pitchVariation = pitchVariation;
        return this;
    }

    /**
     * Plays this skill's sound at the given entity in the given world. This calls {@link Skill#playSound(Level, double, double, double)}, passing in the given entity's position as the xyz coordinates. Also checks if the given entity
     * is silent, and if so, does not play the sound.
     * <p></p>
     * <i>If you are overriding the {@code Skill.playSound} methods, it is recommended that you override the xyz version
     * instead of this one, since this method calls that one anyway - unless you want different behaviour for entities.</i>
     *
     * @param world  The world to play the sound in.
     * @param entity The entity to play the sound at, provided it is not silent.
     */
    protected void playSound(Level world, LivingEntity entity) {
        if (!entity.isSilent()) {
            this.playSound(world, entity.getX(), entity.getY(), entity.getZ());
        }
    }

    /**
     * Plays this skill's sounds at the given position in the given world. This is not called automatically; subclasses
     * should call it at the appropriate point(s) in the cast methods. By default, it checks whether each sound is null
     * before playing, so callers shouldn't have to.
     * <p></p>
     * Usually, this method will be called by the general skill classes; this is clearly stated in those classes. When
     * extending such a class, it is also possible to override this method to add extra sounds or change the sound
     * behaviour entirely (for example, playing a continuous skill sound).
     *
     * @param world The world to play the sound in.
     * @param x     The x position to play the sound at.
     * @param y     The y position to play the sound at.
     * @param z     The z position to play the sound at.
     */
    protected void playSound(Level world, double x, double y, double z) {
        if (this.sounds != null) {
            for (SoundEvent sound : this.sounds) {
                world.playSound(null, x, y, z, sound, SoundSource.PLAYERS, volume, pitch + pitchVariation * (world.getRandom().nextFloat() - 0.5f));
            }
        }
    }
}
